/*
 * Copyright (C) 2011 Wojciech Dzierżanowski
 * See LICENSE.txt for licensing details.
 */

package wdzierzan.downstream.cli;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import wdzierzan.downstream.core.GstPipelineBuilder;
import wdzierzan.downstream.core.GstPipelineBuilder.EncoderChoice;
import wdzierzan.downstream.core.ReceiverThreadRunner;
import wdzierzan.downstream.core.SshGstStreamer;
import wdzierzan.downstream.core.StreamerHandler;


/**
 *
 * @author Wojciech Dzierżanowski <wojciech.dzierzanowski@gmail.com>
 */
public class Main {

    static final String PORT = "port";
    static final String FORMAT = "format";
    static final String MAX_BITRATE = "max-bitrate";
    
    private static Options options;

    public static void main(String[] args) {

        Logger coreLogger = Logger.getLogger(wdzierzan.downstream.core.Streamer.class.getPackage().getName());
        for (Handler h: coreLogger.getHandlers())
            coreLogger.removeHandler(h);
        Handler handler = new Handler() {
            @Override
            public void publish(LogRecord logRecord) {
                System.out.println(logRecord.getSequenceNumber() + ": " + MessageFormat.format(logRecord.getMessage(), logRecord.getParameters()));
            }
            @Override
            public void flush() {
            }
            @Override
            public void close() throws SecurityException {
            }
        };
        handler.setLevel(Level.ALL);
        coreLogger.addHandler(handler);
//        coreLogger.setLevel(Level.ALL);

        options = new Options();
        options.addOption(OptionBuilder.withLongOpt(PORT)
                .withDescription("the port to open on the DEST side for streaming (default: 3000)")
                .hasArg().withArgName("PORT").withType(Number.class)
                .create('P'));
        options.addOption(OptionBuilder.withLongOpt(FORMAT)
                .withDescription("output format, one of ogg (default) and mp3")
                .hasArg().withArgName("FORMAT")
                .create('f'));
        options.addOption(OptionBuilder.withLongOpt(MAX_BITRATE)
                .withDescription("maximum target bitrate in kbps")
                .hasArg().withArgName("BITRATE").withType(Number.class)
                .create('b'));

        CommandLineParser parser = new PosixParser();
        try {
            CommandLine line = parser.parse(options, args);
            String[] cmd_args = line.getArgs();
            if (cmd_args.length < 2) {
                throw new ParseException("Too few arguments");
            }

            Source source = new Source(line);

            int destPort = 3000;
            if (line.hasOption(PORT))
                destPort = ((Number) line.getParsedOptionValue(PORT)).intValue();

            SshGstStreamer streamer = new SshGstStreamer(source.getHostname());
            streamer.setPipeline(makePipeline(destPort, line));
            streamer.setVerbatimPipeline(makeVerbatimPipeline(destPort));

            String extension = '.' + getFormat(line);

            stream(streamer, source, destPort, cmd_args[1], extension);

        } catch (ParseException ex) {
            printHelp();
        }
    }

    private static void printHelp() {
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp(
                "Main [options] [[USER@]HOST:]SRC DEST",
                options);
    }

    private static void stream(SshGstStreamer streamer, Source source,
            int destPort, String destPath, String extension) {

        try {
            streamer.connect(source.getUsername(), source.getPassword());

            StreamerHandler handler = new StreamerHandler(
                    new ReceiverThreadRunner(), new StdoutProgressReport());
            handler.stream(streamer, destPort, source.getPath(), destPath, extension);

        } catch (Exception ex) {
            System.err.println("Streaming failed: " + ex);
            if (ex.getCause() != null)
                System.err.println("   because " + ex.getCause().getMessage());
        } finally {
            streamer.disconnect();
        }
    }

    private static GstPipelineBuilder makePipeline(int destPort, CommandLine line)
            throws ParseException {

        EncoderChoice pipeline = makeVerbatimPipeline(destPort);
        try {
            String hostname = InetAddress.getLocalHost().getHostName();
            pipeline = GstPipelineBuilder.via(hostname, destPort);
        } catch (UnknownHostException ex) {
            throw new RuntimeException(ex);
        }

        String format = getFormat(line);
        if (format.equals("ogg")) {
            return makeOggVorbisPipeline(pipeline, line);
        } else if (format.equals("mp3")) {
            return makeMp3Pipeline(pipeline, line);
        } else {
            throw new UnsupportedOperationException();
        }
    }

    private static EncoderChoice makeVerbatimPipeline(int destPort) throws ParseException {

        try {
            String hostname = InetAddress.getLocalHost().getHostName();
            return GstPipelineBuilder.via(hostname, destPort);
        } catch (UnknownHostException ex) {
            throw new RuntimeException(ex);
        }
    }

    private static GstPipelineBuilder makeOggVorbisPipeline(
            EncoderChoice pipeline, CommandLine line) throws ParseException {

        int max_bitrate = 192;
        if (line.hasOption(MAX_BITRATE))
            max_bitrate = ((Number) line.getParsedOptionValue(MAX_BITRATE)).intValue();

        int[][] bitrates = new int[][] {
            {-1,  45},
            { 0,  64},
            { 1,  80},
            { 2,  96},
            { 3, 112},
            { 4, 128},
            { 5, 160},
            { 6, 192},
            { 7, 224},
            { 8, 256},
            { 9, 320},
        };
        double quality = 1.0;
        for (int[] mapping : bitrates) {
            if (max_bitrate <= mapping[1]) {
                quality = (double) mapping[0] / 10;
                break;
            }
        }

        return pipeline.toOggVorbis().quality(quality);
    }

    private static GstPipelineBuilder makeMp3Pipeline(
            EncoderChoice pipeline, CommandLine line) throws ParseException {

        int max_bitrate = 192;
        if (line.hasOption(MAX_BITRATE))
            max_bitrate = ((Number) line.getParsedOptionValue(MAX_BITRATE)).intValue();

        int[][] bitrates = new int[][] {
            {1006,  128},
            {1001,  192},
            {1002,  256},
        };
        int preset = 1003;
        for (int[] mapping : bitrates) {
            if (max_bitrate <= mapping[1]) {
                preset = mapping[0];
                break;
            }
        }

        return pipeline.toMp3().preset(preset);
    }

    private static String getFormat(CommandLine line) throws ParseException {
        String format = line.getOptionValue(FORMAT, "ogg");
        if (format.equals("ogg") || format.equals("mp3"))
            return format;
        throw new ParseException("Unknown format");
    }
}
